﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using JasLib.Extensions;

namespace JasLib.Interop.Win32
{
    public static partial class EnumExtensions
    {
        private static Dictionary<WindowMessageId, string> s_registeredMessageNames = new Dictionary<WindowMessageId, string>();

        public static bool FAILED(this HRESULT result)
        {
            /// The sign bit is called the "severity" bit and is set for errors.
            return (((uint)result >> 31) == Constants.SEVERITY_ERROR);
        }

        public static Type GetManagedType(this JET_COLTYP columnType)
        {
            switch (columnType)
            {
                case JET_COLTYP.JET_coltypBit: return typeof(bool);
                case JET_COLTYP.JET_coltypUnsignedByte: return typeof(byte);
                case JET_COLTYP.JET_coltypShort: return typeof(short);
                case JET_COLTYP.JET_coltypLong: return typeof(int);
                case JET_COLTYP.JET_coltypCurrency: return typeof(long);
                case JET_COLTYP.JET_coltypIEEESingle: return typeof(float);
                case JET_COLTYP.JET_coltypIEEEDouble: return typeof(double);
                case JET_COLTYP.JET_coltypBinary: return typeof(byte[]);
                case JET_COLTYP.JET_coltypText: return typeof(string);
                case JET_COLTYP.JET_coltypLongBinary: return typeof(byte[]);
                case JET_COLTYP.JET_coltypLongText: return typeof(string);
                case JET_COLTYP.JET_coltypUnsignedLong: return typeof(uint);
                case JET_COLTYP.JET_coltypLongLong: return typeof(long);
                case JET_COLTYP.JET_coltypGUID: return typeof(Guid);
                case JET_COLTYP.JET_coltypUnsignedShort: return typeof(ushort);
                default: return null;
            }
        }

        public static string GetRegisteredMessageName(this WindowMessageId eMessageId)
        {
            string strMessageName = null;
            lock (s_registeredMessageNames)
            {
                if (!s_registeredMessageNames.TryGetValue(eMessageId, out strMessageName))
                {
                    /// Evidently from the documentation, clipboard format strings and registered window message strings occupy the same 
                    /// unsigned integer range in the global atom table. Therefore this function can return either,
                    /// but due to the identical way they're created and stored, the registered window messages can't be separately enumerated.
                    strMessageName = USER32.GetClipboardFormatName((uint)eMessageId);
                    if (strMessageName != null)
                    {
                        strMessageName = string.Intern(strMessageName);
                        s_registeredMessageNames.Add(eMessageId, strMessageName);
                    }
                }
            }
            return strMessageName;
        }

        public static ushort HRESULT_CODE(this HRESULT result)
        {
            return (ushort)((uint)result & 0x0000FFFF);
        }

        public static HResultFacility HRESULT_FACILITY(this HRESULT result)
        {
            return (HResultFacility)(((int)(uint)result >> 16) & 0x00001FFF);
        }

        public static HRESULT HRESULT_FROM_JET_ERR(this JET_ERR error)
        {
            return MAKE_HRESULT(error < 0, HResultFacility.FACILITY_JET_ERR, (ushort)error);
        }

        /// <summary>
        /// Map an NT status value into a HRESULT
        /// </summary>
        public static HRESULT HRESULT_FROM_NT(this NTSTATUS x)
        {
            return ((HRESULT)((x) | (NTSTATUS)Constants.FACILITY_NT_BIT));
        }

        public static HRESULT HRESULT_FROM_WIN32(this Win32ErrorCode x)
        {
            return MAKE_HRESULT(x != Win32ErrorCode.ERROR_SUCCESS, HResultFacility.FACILITY_WIN32, (ushort)x);
        }

        public static bool IsKeyboardMessage(this WindowMessageId eMessageId)
        {
            return (Constants.WM_KEYFIRST <= eMessageId) && (eMessageId <= Constants.WM_KEYLAST);
        }

        public static bool IsMouseMessage(this WindowMessageId eMessageId)
        {
            return (Constants.WM_MOUSEFIRST <= eMessageId) && (eMessageId <= Constants.WM_MOUSELAST);
        }

        public static bool IsUserMessage(this WindowMessageId eMessageId)
        {
            return (WindowMessageId.WM_USER <= eMessageId) && (eMessageId <= (WindowMessageId)0x7FFF);
        }

        public static bool IsAppMessage(this WindowMessageId eMessageId)
        {
            return (WindowMessageId.WM_APP <= eMessageId) && (eMessageId <= (WindowMessageId)0xBFFF);
        }

        public static bool IsInformationType(this NTSTATUS status)
        {
            uint uiCode = (uint)status;
            return ((0x40000000 <= uiCode) && (uiCode <= 0x7FFFFFFF));
        }

        public static bool IsRegisteredMessage(this WindowMessageId eMessageId)
        {
            return (Constants.RegisteredMessageIds.First <= eMessageId) && (eMessageId <= Constants.RegisteredMessageIds.Last);
        }

        public static bool IsSuccessType(this NTSTATUS status)
        {
            uint uiCode = (uint)status;
            return ((0 <= uiCode) && (uiCode <= 0x3FFFFFFF)) || ((0x40000000 <= uiCode) && (uiCode <= 0x7FFFFFFF));
        }

        public static bool IsWarningType(this NTSTATUS status)
        {
            uint uiCode = (uint)status;
            return ((0x80000000 <= uiCode) && (uiCode <= 0xBFFFFFFF));
        }

        public static bool IsErrorType(this NTSTATUS status)
        {
            uint uiCode = (uint)status;
            return ((0xC0000000 <= uiCode) && (uiCode <= 0xFFFFFFFF));
        }

        public static bool IsSuccessSeverity(this PDH_STATUS status)
        {
            return ((status & PDH_STATUS.SeverityMask) == PDH_STATUS.SuccessMask);
        }

        public static bool IsInformationalSeverity(this PDH_STATUS status)
        {
            return ((status & PDH_STATUS.SeverityMask) == PDH_STATUS.InformationalMask);
        }

        public static bool IsWarningSeverity(this PDH_STATUS status)
        {
            return ((status & PDH_STATUS.SeverityMask) == PDH_STATUS.WarningMask);
        }

        public static bool IsErrorSeverity(this PDH_STATUS status)
        {
            return ((status & PDH_STATUS.SeverityMask) == PDH_STATUS.ErrorMask);
        }

        public static HRESULT MAKE_HRESULT(bool bIsError, HResultFacility fac, ushort code)
        {
            uint uiResult = (uint)code;
            uiResult |= ((uint)fac << 16);
            if (bIsError)
                uiResult |= 0x80000000;
            return (HRESULT)uiResult;
        }

        public static LANGID MAKELANGID(PrimaryLanguageId usPrimaryLanguage, SubLanguageId usSubLanguage)
        {
            return (LANGID)((((ushort)usSubLanguage) << 10) | (ushort)usPrimaryLanguage);
        }

        public static LCID MAKELCID(LANGID languageId, SortLanguageId wSortID)
        {
            return (LCID)((uint)((((uint)((ushort)(wSortID))) << 16) | ((uint)((ushort)(languageId)))));
        }

        public static PrimaryLanguageId PRIMARYLANGID(this LANGID languageId)
        {
            return (PrimaryLanguageId)((ushort)languageId & 0x3ff);
        }

        public static SubLanguageId SUBLANGID(this LANGID languageId)
        {
            return (SubLanguageId)((ushort)languageId >> 10);
        }

        public static bool SUCCEEDED(this HRESULT result)
        {
            /// The sign bit is called the "severity" bit and is set for errors.
            return (((uint)result >> 31) == Constants.SEVERITY_SUCCESS);
        }

        public static COMException ToCOMException(this HRESULT hResult, string strMessage = "")
        {
            return new COMException(strMessage, hResult);
        }

        public static string ToErrorString(this JET_ERR errorCode)
        {
            /// Special mode that returns actual error strings.
            UINT_PTR uiErrorCode = (int)errorCode;
            StringBuilder sb = new StringBuilder(256 /* actual limit unknown */);
            if (0 > ESENT.JetGetSystemParameter(
                JET_INSTANCE.NULL,
                JET_SESID.JET_sesidNil,
                JetSystemParameterCode.JET_paramErrorToString,
                ref uiErrorCode, sb, (uint)sb.Capacity))
            {
                return null;
            }

            string strMessage = sb.ToString();
            strMessage = strMessage.Substring(strMessage.IndexOf(',') + 2);
            return strMessage;
        }

        public static Exception ToNtException(this NTSTATUS status)
        {
            if (!IsErrorType(status))
                return null;

            return new Exception(string.Format("NTSTATUS = {0}", status));
        }

        /// <summary>
        /// This throws a COMException based on the provided HRESULT if it contains an error code.
        /// </summary>
        public static void TryThrowCOMException(this HRESULT hResult, string strMessage = "")
        {
            if (hResult.FAILED())
                throw new COMException(strMessage, hResult);
        }

        /// <summary>
        /// This throws an EsentException based on the provided JET_ERR if it contains an error code.
        /// </summary>
        /// <returns>Always returns false. This allows the function to be used as loop conditions.</returns>
        public static bool TryThrowEsentException(
            this JET_ERR errorCode,
            string strMessage = "",
            params object[] args)
        {
            if (errorCode < 0)
                throw new EsentException(errorCode, strMessage.FormatWith(args));
            return false;
        }

        public static void TryThrowPdhException(
            this PDH_STATUS status,
            string strMessage = "",
            params object[] args)
        {
            if ((Win32ErrorCode)status != Win32ErrorCode.ERROR_SUCCESS)
            {
                if (IsErrorSeverity(status))
                {
                    throw new Exception(status.ToString());
                }
                else if (!Enum.IsDefined(typeof(PDH_STATUS), status))
                {
                    ((Win32ErrorCode)status).TryThrowWin32Exception(strMessage, args);
                }
            }
        }

        public static void TryThrowNtException(
            this NTSTATUS status,
            string strMessage = "",
            params object[] args)
        {
            if (IsErrorType(status))
            {
                Exception exception = ToNtException(status);
                if (exception != null)
                    throw exception;
            }
        }

        public static void TryThrowWin32Exception(
            this Win32ErrorCode errorCode,
            string strMessage = "",
            params object[] args)
        {
            if (errorCode != Win32ErrorCode.ERROR_SUCCESS)
                throw new Win32Exception(errorCode, strMessage.FormatWith(args));
        }
    }
}
